package com.goodsogood.union.auth.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.goodsogood.union.auth.conf.LogConfiger;
import com.goodsogood.union.auth.conf.TokenExpireConfig;
import com.goodsogood.union.auth.constant.KeyConst;
import com.goodsogood.union.auth.exception.TokenDeviceNotMatchException;
import com.goodsogood.union.auth.exception.TokenExpiredException;
import com.goodsogood.union.auth.exception.TokenNotFoundException;
import com.goodsogood.union.auth.redis.RedisPool;
import com.goodsogood.union.auth.service.AccessToken;
import com.goodsogood.union.auth.service.PlateformDeviceType;
import com.goodsogood.union.auth.service.UserTokenService;
import com.goodsogood.union.auth.util.TokenUtil;

import redis.clients.jedis.Jedis;

@Service("userTokenService")
@Transactional
public class RedisUserTokenService implements UserTokenService {
	private static final Logger logger = LoggerFactory.getLogger(LogConfiger.APP);

	@Autowired
	private RedisPool redisPool;

	@Override
	public void refresh(String UserID) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			jedis.expire(KeyConst.KEY_USER_ACCESS_TOKEN + UserID, TokenExpireConfig.USER_ACCESS_TOKEN_EXPIRE);
		} catch (Exception e) {
			logger.error("RedisTokenService refresh happen error , UserID is {},error is ", UserID, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	private void del(String key) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			jedis.del(key);
		} catch (Exception e) {
			logger.error("RedisTokenService del happen error , key is {},error is ", key, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	public void refresh(String UserID, String tokenId) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			AccessToken accessToken = findAccessToken(UserID, tokenId);
			accessToken.setLastAccessTime(System.currentTimeMillis());
			addToken(UserID, accessToken);
			refresh(UserID);
		} catch (Exception e) {
			logger.error("RedisTokenService refresh happen error , UserID is {},error is ", UserID, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	@Override
	public void disbaleTokenOnOtherDevice(PlateformDeviceType plateformDeviceType, String userId) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			Map<String, AccessToken> tokens = findUserAccessTokens(userId);
			if (tokens.size() > 0) {
				Iterator<Map.Entry<String, AccessToken>> it = tokens.entrySet().iterator();
				while (it.hasNext()) {
					Map.Entry<String, AccessToken> entry = it.next();
					AccessToken token = entry.getValue();
					if (token.getPlateformDeviceType().startsWith(
							plateformDeviceType.getPlateform() + KeyConst.SEPARATOR + plateformDeviceType.getApp())) {
						it.remove();
						addLogoutNotifyMessage(token.getId());
					}
				}
				setTokens(userId, tokens);
			}
		} catch (Exception e) {
			logger.error(
					"RedisTokenService disbaleOnOtherPlateform happen error , plateformDeviceType is {},userId is {},error is ",
					plateformDeviceType, userId, e);
		} finally {
			redisPool.returnResource(jedis);
		}

	}

	@Override
	public void addLogoutNotifyMessage(String tokenId) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			jedis.set(KeyConst.KEY_USER_TOKEN_LOGOUT + tokenId, "您的设备已在别处登录。！");
			jedis.expire(KeyConst.KEY_USER_TOKEN_LOGOUT + tokenId, TokenExpireConfig.USER_LOGOUT_MESS_EXPIRE);
		} catch (Exception e) {
			logger.error("RedisTokenService remove happen error , tokenId is {},userId is {},error is ", tokenId, "",
					e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	@Override
	public String findLogoutNotifyMessage(String tokenId) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			String mess = jedis.get(KeyConst.KEY_USER_TOKEN_LOGOUT + tokenId);
			if (mess != null) {
				jedis.del(KeyConst.KEY_USER_TOKEN_LOGOUT + tokenId);
			}
			return mess;
		} catch (Exception e) {
			logger.error("RedisTokenService remove happen error , plateformDeviceType is {},userId is {},error is ",
					tokenId, "", e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	private Map<String, AccessToken> findUserAccessTokens(String userId) {
		Jedis jedis = null;
		String json = null;
		Map<String, AccessToken> tokenMap = new HashMap<String, AccessToken>();
		try {
			jedis = redisPool.getResource();
			json = jedis.get(KeyConst.KEY_USER_ACCESS_TOKEN + userId);
			if (json != null)
				tokenMap = (JSON.parseObject(json, new TypeReference<Map<String, AccessToken>>() {
				}));
		} catch (Exception e) {
			logger.error("RedisTokenService setToken happen error , tokenContent is {},userId is {},error is ", json,
					userId, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
		return tokenMap;
	}

	public AccessToken findAccessToken(String userId, String tokenId) {
		return findUserAccessTokens(userId).get(tokenId);
	}

	@Override
	public void addToken(String userId, AccessToken token) {
		Jedis jedis = null;
		try {
			jedis = redisPool.getResource();
			Map<String, AccessToken> tokens = findUserAccessTokens(userId);
			tokens.put(token.getId(), token);
			setTokens(userId, tokens);
			jedis.set(KeyConst.KEY_USER_ACCESS_TOKEN + userId, JSON.toJSONString(tokens));
			jedis.expire(KeyConst.KEY_USER_ACCESS_TOKEN + userId, TokenExpireConfig.USER_ACCESS_TOKEN_EXPIRE);
		} catch (Exception e) {
			logger.error("RedisTokenService setToken happen error , tokenContent is {},userId is {},error is ",
					token.toString(), userId, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	@Override
	public void setTokens(String userId, Map<String, AccessToken> tokens) {
		Jedis jedis = null;
		String json = null;
		try {
			json = JSON.toJSONString(tokens);
			jedis = redisPool.getResource();
			jedis.set(KeyConst.KEY_USER_ACCESS_TOKEN + userId, JSON.toJSONString(tokens));
			refresh(userId);
		} catch (Exception e) {
			logger.error("RedisTokenService setTokens happen error , tokenContent is {},userId is {},error is ",
					json.toString(), userId, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
	}

	@Override
	public boolean verify(String token, String plateformDeviceType, String plateformDeviceInfo) {
		AccessToken accessToken = TokenUtil.decryptToken(token);
		AccessToken accessTokenInRedis = findAccessToken(accessToken.getUserId(), accessToken.getId());
		if (accessTokenInRedis == null)
			throw new TokenNotFoundException(JSON.toJSONString(accessToken));
		if (accessTokenInRedis.isExpired())
			throw new TokenExpiredException();
		if (accessTokenInRedis.getPlateformDeviceType().equals(plateformDeviceType)
				&& accessTokenInRedis.getDeviceId().equals(plateformDeviceInfo)) {
			refresh(accessToken.getUserId(), accessToken.getId());
			return true;
		} else {
			System.out.println(accessTokenInRedis.getPlateformDeviceType().equals(plateformDeviceType));
			System.out.println(accessTokenInRedis.getDeviceId().equals(plateformDeviceInfo));
			throw new TokenDeviceNotMatchException("ClientToken device['" + plateformDeviceType + "','"
					+ plateformDeviceInfo + "'],RedisToken device['" + accessTokenInRedis.getPlateformDeviceType()
					+ "','" + accessTokenInRedis.getDeviceId() + "']");
		}
	}
	@Override
	public boolean verify(String token) {
		AccessToken accessToken = TokenUtil.decryptToken(token);
		String tokenRedis = findAuthToken(accessToken.getUserId());
		return token.equals(tokenRedis);
	}

	private String findAuthToken(String userId) {
		Jedis jedis = null;
		String token = null;
		try {
			jedis = redisPool.getResource();
			token = jedis.get(KeyConst.KEY_ADMIN_TOKEN + userId);
			
		} catch (Exception e) {
			logger.error("RedisTokenService findAuthToken happen error , userId is {},error is ", 
					userId, e);
			throw e;
		} finally {
			redisPool.returnResource(jedis);
		}
		return token;
	}

	@Override
	public boolean verifyAuth(String token, String url) {
		AccessToken accessToken = TokenUtil.decryptToken(token);
		//1、根据用户ID获取此用户的角色列表
		List<String> roleIds = getAdminRoleIds(accessToken.getUserId());
		if(roleIds==null || roleIds.isEmpty()){
			return false;
		}
		//2、获取所有角色列表对应的权限列表
		List<String> authUrls = getUrlsByRoleIds(roleIds);
		//3、进行权限匹配
		if(authUrls!=null && !authUrls.isEmpty()&&authUrls.contains(url)){
			return true;
		}
		return false;
	}

	private List<String> getUrlsByRoleIds(List<String> roleIds) {
		List<String> result = new ArrayList<>();
		for(String roleId : roleIds){
			List<String> list = getRoleAuthList(roleId);
			if(list!=null && !list.isEmpty()){
				result.addAll(list);
			}
		}
		return result;
	}

	private List<String> getRoleAuthList(String roleId) {
		String key = KeyConst.KEY_ROLE_AUTHS+roleId;
        Jedis jedis = null; 
        try {
            jedis = redisPool.getResource();
            if(!jedis.exists(key)){
            	return null;
            }
           	long len = jedis.llen(key);
           	List<String> roleAuths = jedis.lrange(key, 0, len-1);
            return roleAuths;
        } catch (Exception e) {
            logger.error("getRoleAuthList happen error , roleId is {}, error is ", roleId, e);
            return null;
        } finally {
            redisPool.returnResource(jedis);
        }
	}

	private List<String> getAdminRoleIds(String adminId) {
		String key = KeyConst.KEY_ADMIN_ROLES+adminId;
        Jedis jedis = null; 
        try {
            jedis = redisPool.getResource();
            if(!jedis.exists(key)){
            	return null;
            }
           	long len = jedis.llen(key);
           	List<String> roles = jedis.lrange(key, 0, len-1);
            return roles;
        } catch (Exception e) {
            logger.error("getAdminRoleIds happen error , adminId is {}, error is ", adminId, e);
            return null;
        } finally {
            redisPool.returnResource(jedis);
        }
	}

}
